Skip to content

Fix string instance method closures on JS, Python, and JVM targets#12784

Draft
Copilot wants to merge 4 commits intodevelopmentfrom
copilot/fix-string-charat-closure
Draft

Fix string instance method closures on JS, Python, and JVM targets#12784
Copilot wants to merge 4 commits intodevelopmentfrom
copilot/fix-string-charat-closure

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 10, 2026

Taking a reference to a String instance method (e.g. str.charAt) crashes or produces wrong results on several targets. The typed case (f.charAt) and dynamic case ((d:Dynamic).charAt) are both affected.

var f = "foo";
var o = {charAt: f.charAt};  // JS: TypeError: Cannot assign to read only property 'hx__closures__'
trace(o.charAt(0));           // => "f"

var d:Dynamic = "bar";
var fn = d.charAt;            // JS: unbound, fails in strict mode; Python: compile error
trace(fn(0));                 // => "b"

JavaScript (src/generators/genjs.ml)

  • $bind fix: Skip hx__closures__ caching when typeof o !== "object" (primitives are immutable). Use m.bind(o) directly instead, which avoids the crash.
  • $sbind helper: For dynamic/structural field accesses on potentially-string expressions (field name in charAt, indexOf, split, etc.), emit $sbind(o, "f") instead of o.f in value context. The helper is function $sbind(o,f) { var m = o[f]; if( typeof m !== "function" ) return m; return m.bind(o); } — uses direct .bind() without caching to avoid adding hx__closures__ as an enumerable property (which would corrupt js.Boot.__string_rec and similar serialization).
  • Call-context bypass: Direct calls like d.charAt(0) are generated as d.charAt(0) (JS auto-binds this for dot-notation), skipping the $sbind wrapper.

Python (std/python/_std/String.hx)

All inline instance methods were missing @:runtime, causing a compile-time error ("Can't create closure on an extern inline member method"). Added @:runtime to all of them; the Python generator already has correct FClosure handling via python_Boot.createClosure.

JVM (src/generators/genjvm.ml, std/jvm/_std/String.hx)

  • Generator: FClosure(Some(String,_), cf) was calling java.lang.String.charAt(int) directly, which returns char not String, causing NoSuchMethodError. Redirected these closures through Jvm.readField, which already dispatches all String methods via StringExt wrappers.
  • String.hx: charCodeAt and indexOf were inline without @:runtime, preventing closure creation at the type-checker level.

Test

Issue8068.hx target guard extended from hl || lua || interp || php to also include js || python || jvm.

Original prompt

This section details on the original issue you should resolve

<issue_title>[js] Passing String.charAt takes weird closure</issue_title>
<issue_description>``` haxe
class Main {
static function main() {
var str = "fdsf";
run(str.charAt);
}

static function run(cb:Int->String) trace(cb(2));

}


Generated:

``` js
// Generated by Haxe 3.3.0
(function () { "use strict";
var Main = function() { };
Main.main = function() {
    Main.run($bind("fdsf","fdsf".charAt));
};
Main.run = function(cb) {
    console.log(cb(2));
};
var $_, $fid = 0;
function $bind(o,m) { if( m == null ) return null; if( m.__id__ == null ) m.__id__ = $fid++; var f; if( o.hx__closures__ == null ) o.hx__closures__ = {}; else f = o.hx__closures__[m.__id__]; if( f == null ) { f = function(){ return f.method.apply(f.scope, arguments); }; f.scope = o; f.method = m; o.hx__closures__[m.__id__] = f; } return f; }
Main.main();
})();

Errors with TypeError: Cannot assign to read only property 'hx__closures__' of fdsf. Not a regression and might be broken on other targets too.
</issue_description>

<agent_instructions>a612ec5#diff-82ff41117d08266d5e5c3d7b7565c8b7a4504ae8c71c03ab7bf7559a60f384e2 has added a test that fails on various targets, see the #if condition there. Let's start tackling that and making it work on more/all targets!</agent_instructions>

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix string.charAt closure in generated JS Fix string instance method closures on JS, Python, and JVM targets Mar 10, 2026
Copilot finished work on behalf of Simn March 10, 2026 16:31
@Simn
Copy link
Copy Markdown
Member

Simn commented Mar 10, 2026

The python and JVM changes are fine, but JS, oof...

Simn added a commit that referenced this pull request Mar 10, 2026
@RblSb
Copy link
Copy Markdown
Member

RblSb commented Mar 11, 2026

This needs optimization test if this is_possible_string_field will be keeped. Because this test can pass without it, but probably by generating more closures on Dynamic stuff.

First part of this test is one-line fix in $bind, pretty sad that we cannot just compile-error on (String to Dynamic).field to ref thing for js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[js] Passing String.charAt takes weird closure

3 participants